package pl.poznan.put.cs.idss.students.coolring.core.impl;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import pl.poznan.put.cs.idss.students.coolring.core.IAlgorithm;
import pl.poznan.put.cs.idss.students.coolring.ui.components.ColoredPoint;

/**
 * Implementacja algorytmu kolorujacego. Algorytm pobiera kolory oznaczeń i
 * rozlewa je uwzględniając jasność kolejnych pikseli. W momencie kiedy różnica
 * jasnosci przekroczy pewną wartosć (ustaloną dla algorytmu) rozlewanie w danym
 * kierunku jest przerywane.
 * 
 * <b>UWAGA!</b> W takcie implementacji, wersja bardzo niedopracowana!
 * 
 * @author Kornel Lewandowski
 * 
 */
public class KornelColoring implements IAlgorithm, ImageObserver {

	boolean[][] colored = new boolean[640][480];
	int[][] colors = new int[640][480];

	private BufferedImage inputImage;
	private List<ColoredPoint> points = new ArrayList<ColoredPoint>();
	private double progress = 0.0;
	private Image outputImage;
	private int leftToPaint = 640 * 480;
	private boolean secondPhase = false;

	Set<Point> pH = new HashSet<Point>();

	private int pointsCounterLeftTop = 0;

	public void setImage(Image bwImage) {
		inputImage = new BufferedImage(640, 480, 3);
		inputImage.getGraphics().drawImage(bwImage, 0, 0, 640, 480,
				Color.WHITE, this);
	}

	public void setPointsList(List<ColoredPoint> pointsList) {
		points = pointsList;
	}

	public double getProgress() {
		return progress;
	}

	public Image getReslt() {
		return outputImage;
	}

	public void run() {
		outputImage = new BufferedImage(640, 480, 3);
		Graphics g = outputImage.getGraphics();
		g.drawImage(inputImage, 0, 0, this);
		for (ColoredPoint point : points) {

			if (point.getX() > 0 && point.getX() < 640 && point.getY() > 0
					&& point.getY() < 480) {

				float[] hsb = { 0, 0, 0 };
				Color.RGBtoHSB(
						new Color(inputImage.getRGB(point.getX(), point.getY()))
								.getRed(),
						new Color(inputImage.getRGB(point.getX(), point.getY()))
								.getGreen(),
						new Color(inputImage.getRGB(point.getX(), point.getY()))
								.getBlue(), hsb);

				pointsCounterLeftTop = 0;
				paintPixelLeftTop(g, point.getX(), point.getY(), hsb[2], point,
						-1, -1);
				pointsCounterLeftTop = 0;
				paintPixelLeftTop(g, point.getX(), point.getY(), hsb[2], point,
						+1, +1);
				pointsCounterLeftTop = 0;
				paintPixelLeftTop(g, point.getX(), point.getY(), hsb[2], point,
						-1, +1);
				pointsCounterLeftTop = 0;
				paintPixelLeftTop(g, point.getX(), point.getY(), hsb[2], point,
						+1, -1);

				progress += 0.5 / points.size();
			}

		}

		System.out.println("LeftToPaint: " + leftToPaint);

		int lastLeftToPaint = 0;

		secondPhase = true;

		for (int it = 0; it < 4; it++) {

			if (lastLeftToPaint == leftToPaint)
				break;

			lastLeftToPaint = leftToPaint;

			System.out.println("Still not all, points left: " + leftToPaint);

			for (int x = 0; x < 640; x++) {
				for (int y = 0; y < 480; y++) {

					ColoredPoint point = new ColoredPoint(0, 0, Color.black);

					if (!colored[x][y]) {
						Map<Integer, Integer> weights = new HashMap<Integer, Integer>();
						for (int i = x - 3; i < x + 3; i++) {
							for (int j = y - 3; j < y + 3; j++) {
								if (i > 0 && i < 640 && j > 0 && j < 480) {
									int color = colors[i][j];
									if (colored[i][j] && colors[i][j] != 0) {
										if (weights.containsKey(color)) {
											int value = weights.get(color);
											value += 1;
											weights.remove(color);
											weights.put(color, value);
										} else {
											weights.put(color, 1);
										}
									}
								}
							}
						}

						int finalColor = 0;
						int max = 0;
						int sum = 0;

						for (Integer c : weights.keySet()) {
							sum += weights.get(c);
							if (weights.get(c) > max && c != 0) {
								max = weights.get(c);
								finalColor = c;
							}
						}

						if (finalColor != 0) {

							if (2 * max >= sum) {
								point = new ColoredPoint(x, y, new Color(
										finalColor));

								float[] hsb = { 0, 0, 0 };
								Color.RGBtoHSB(
										new Color(inputImage.getRGB(
												point.getX(), point.getY()))
												.getRed(),
										new Color(inputImage.getRGB(
												point.getX(), point.getY()))
												.getGreen(),
										new Color(inputImage.getRGB(
												point.getX(), point.getY()))
												.getBlue(), hsb);

								pointsCounterLeftTop = 0;
								paintPixelLeftTop(g, x, y, hsb[2], point, -1,
										-1);
								pointsCounterLeftTop = 0;
								paintPixelLeftTop(g, x, y, hsb[2], point, +1,
										+1);
								pointsCounterLeftTop = 0;
								paintPixelLeftTop(g, x, y, hsb[2], point, -1,
										+1);
								pointsCounterLeftTop = 0;
								paintPixelLeftTop(g, x, y, hsb[2], point, +1,
										-1);

							}

						}

					}

				}

				progress += 0.5 / it;

			}

			System.out.println("LeftToPaint (0.1%): " + leftToPaint);

			progress = 1.0;

		}

	}

	private void paintPixelLeftTop(Graphics g, int x, int y, float brightness,
			ColoredPoint source, int dirX, int dirY) {

		if (x <= 1
				|| x >= 639
				|| y <= 1
				|| y >= 479
				|| x - source.getX() > 320
				|| source.getX() - x > 320
				|| y - source.getY() > 240
				|| source.getY() - y > 240
				|| pointsCounterLeftTop > 640 * 580 * 4
				|| (colored[x][y] && secondPhase)
				|| (pH.contains(new Point(x, y)) && x != source.getX() && y != source
						.getY())) {
			return;
		}

		pH.add(new Point(x, y));
		pointsCounterLeftTop = pH.size();
		if (!colored[x][y])
			leftToPaint--;
		colored[x][y] = true;

		float hsb[] = { 0, 0, -1 };

		Color.RGBtoHSB(new Color(inputImage.getRGB(x, y)).getRed(), new Color(
				inputImage.getRGB(x, y)).getGreen(),
				new Color(inputImage.getRGB(x, y)).getBlue(), hsb);

		float currentBrightness = hsb[2];

		Color.RGBtoHSB(source.getColor().getRed(),
				source.getColor().getGreen(), source.getColor().getBlue(), hsb);

		hsb[2] = currentBrightness;

		Color c = new Color(Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]));

		colors[x][y] = c.getRGB();

		g.setColor(c);
		g.drawLine(x, y, x, y);

		Color.RGBtoHSB(new Color(inputImage.getRGB(x + dirX, y)).getRed(),
				new Color(inputImage.getRGB(x + dirX, y)).getGreen(),
				new Color(inputImage.getRGB(x + dirX, y)).getBlue(), hsb);

		float verticalNeighbourBrightness = hsb[2];

		Color.RGBtoHSB(new Color(inputImage.getRGB(x, y + dirY)).getRed(),
				new Color(inputImage.getRGB(x, y + dirY)).getGreen(),
				new Color(inputImage.getRGB(x, y + dirY)).getBlue(), hsb);

		float horizontalNeighbourBrightness = hsb[2];

		if (Math.abs(verticalNeighbourBrightness - brightness) < 0.10) {
			paintPixelLeftTop(g, x + dirX, y, brightness, source, dirX, dirY);
		}
		if (Math.abs(horizontalNeighbourBrightness - brightness) < 0.10) {
			paintPixelLeftTop(g, x, y + dirY, brightness, source, dirX, dirY);
		}

	}

	public boolean imageUpdate(Image img, int infoflags, int x, int y,
			int width, int height) {
		return false;
	}
}
